package cn.leaf.calculator.datasource;

import cn.leaf.calculator.bean.CalUnitBeanDefinition;
import cn.leaf.calculator.bean.ParameterBeanDefinition;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.*;

/**
 * 抽象的数据源
 *
 * @author 李克国
 * @version 1.0.0

 */
@Slf4j
public abstract class AbstractCalUnitDataSource<T> implements CalUnitDataSource<T> {

    /**
     * 需要计算的数据信息
     */
    private Collection<T> values;

    /**
     * 需要计算的计算单元源信息
     */
    private Set<CalUnitBeanDefinition<T>> calUnitBeans;

    /**
     * 本地参数缓存池
     */
    private Map<Serializable, ParameterBeanDefinition<T>> localParameterBeans;

    /**
     * 计算单元转换器
     */
    private Converter<T, ? extends CalUnitBeanDefinition<T>> calUnitConverter;

    /**
     * 外部依赖的参数id，临时存储
     */
    private Collection<Serializable> externalParameterIds;

    /**
     * 迭代器
     */
    private Iterator<CalUnitBeanDefinition<T>> iterator;


    public AbstractCalUnitDataSource(Collection<T> values) {
        this.values = values;
    }

    public AbstractCalUnitDataSource() {
    }

    /**
     * 此方法需提供，公式中依赖的外部参数
     *
     * @param parameterIds 外部依赖的公式ids
     * @return list
     */
    public abstract Collection<T> bindUpdateParameters(Collection<Serializable> parameterIds);

    @Override
    public Collection<CalUnitBeanDefinition<T>> getCalUnitBeans() {
        return calUnitBeans;
    }

    @Override
    public void setValues(Collection<T> values) {
        this.values = values;
    }

    @Override
    public Collection<T> getValues() {
        return values;
    }

    @Override
    public void initialize() {
        // 准备
        preprocess();
        // 分析计算单元bean定义
        analysisCalUnitBeans();
        // 装配参数
        assemblyParameters();
        log.info("公式计算数据源装配参数完成");

        iterator = calUnitBeans.iterator();
    }

    @Override
    public void refresh() {
        // 清除信息
        clear(calUnitBeans);
        clear(externalParameterIds);
        if (!localParameterBeans.isEmpty()) {
            localParameterBeans.clear();
        }
        // 重新分析
        analysisCalUnitBeans();
        // 重新装配
        assemblyParameters();

        iterator = calUnitBeans.iterator();
    }

    @Override
    public void clear() {
        clear(values);
        clear(calUnitBeans);
        clear(externalParameterIds);
        if (!localParameterBeans.isEmpty()) {
            localParameterBeans.clear();
        }

    }

    @Override
    public void add(T element) {
        Assert.notNull(element, "新增的计算项不能为空");
        values.add(element);
        CalUnitBeanDefinition<T> bean = analysisCalUnitBean(element);
        assemblyParameter(bean);
        if (externalParameterIds.size() > 0) {
            updateLocalParameters();
            assemblyParameter(bean);
        }
    }

    @Override
    public void remove(Serializable id) {
        calUnitBeans.removeIf(c -> c.getId().equals(id));
    }

    @Override
    public void setConverter(Converter<T, ? extends CalUnitBeanDefinition<T>> converter) {
        this.calUnitConverter = converter;
    }

    @Override
    public boolean hasNext() {
        return iterator.hasNext();
    }

    @Override
    public CalUnitBeanDefinition<T> next() {
        return iterator.next();
    }

    /**
     * 预处理，初始化预处理，对所用容器进行初始化
     */
    private void preprocess() {
        if (values == null) {
            values = new HashSet<>(16);
        }
        if (calUnitBeans == null) {
            calUnitBeans = new HashSet<>(values.size());
        }
        if (localParameterBeans == null) {
            localParameterBeans = new HashMap<>(values.size() * 3);
        }
        if (externalParameterIds == null) {
            externalParameterIds = new ArrayList<>(values.size() * 2);
        }
        log.info("公式计算数据源初始化准备完成");
    }

    /**
     * 分析计算单元源信息
     */
    private void analysisCalUnitBeans() {
        for (T value : values) {
            analysisCalUnitBean(value);
        }
        log.info("公式计算数据源分析计算单元完成");
    }

    /**
     * 分析单个计算单元源信息
     *
     * @return 分析完成后的{@link CalUnitBeanDefinition} 其状态为{@link CalUnitBeanDefinition#CALUNIT_STATE_INITIALIZE}
     */
    private CalUnitBeanDefinition<T> analysisCalUnitBean(T value) {
        // 转你为计算单元bean
        CalUnitBeanDefinition<T> definition = calUnitConverter.convert(value);
        // 对计算单元初始化
        Objects.requireNonNull(definition).resolve();
        definition.updateState(CalUnitBeanDefinition.CALUNIT_STATE_INITIALIZE);
        // 加入到计算单元信息
        calUnitBeans.add(definition);
        // 放入到本地参数池
        localParameterBeans.put(definition.getId(), definition);
        return definition;
    }

    /**
     * 分析参数源信息，添加都本地参数库
     */
    private void analysisParameterBeans(Collection<T> externalParameters) {
        for (T value : externalParameters) {
            // 转你为参数
            ParameterBeanDefinition<T> definition = calUnitConverter.convert(value);
            Assert.notNull(definition, "CalDataSourceException:在外部依赖的参数转换为ParameterBeanDefinition时，转换结果为空");
            definition.updateState(ParameterBeanDefinition.PARAMETER_STATE_INITIALIZE);
            // 放入到本地参数池
            localParameterBeans.put(definition.getId(), definition);
        }
    }

    /**
     * 装配参数
     */
    private void assemblyParameters() {
        // 分析其依赖在本地参数
        for (CalUnitBeanDefinition<T> definition : calUnitBeans) {
            // 装配参数
            assemblyParameter(definition);
        }
        if (externalParameterIds.size() > 0) {
            updateLocalParameters();
            assemblyParameters();
        }
    }

    /**
     * 装配单个参数
     *
     * @param definition {@link CalUnitBeanDefinition}
     */
    private void assemblyParameter(CalUnitBeanDefinition<T> definition) {
        Map<Serializable, ParameterBeanDefinition<T>> parameters = definition.getParameters();
        for (Serializable id : parameters.keySet()) {
            ParameterBeanDefinition<T> lookupByLocalParam = lookupByLocalParams(id);
            if (lookupByLocalParam != null) {
                // 参数为此计算的计算项:进入等待区
                if (lookupByLocalParam.getState() != ParameterBeanDefinition.PARAMETER_STATE_INITIALIZE) {
                    definition.updateState(CalUnitBeanDefinition.CALUNIT_STATE_CAL_WAIT);
                }
                // 参数为计算项，并且其参数中包含当前计算项，定义为循环引用
                if (lookupByLocalParam instanceof CalUnitBeanDefinition
                        && lookupByLocalParam.getState() != ParameterBeanDefinition.PARAMETER_STATE_INITIALIZE
                        && ((CalUnitBeanDefinition<T>) lookupByLocalParam).getParameters().containsKey(definition.getId())) {
                    definition.updateState(CalUnitBeanDefinition.CALUNIT_STATE_CAL_WAIT_WHILE_REF);
                }
                // 装配具体参数依赖
                parameters.put(id, lookupByLocalParam);
                // 在本地参数池中存在，从外部依赖库中删除
                externalParameterIds.remove(id);
            } else {
                // 参数为外部依赖参数，并且未拉取到本地参数池
                externalParameterIds.add(id);
            }
        }
        // 自身引用
        if (parameters.containsKey(definition.getId())) {
            definition.updateState(CalUnitBeanDefinition.CALUNIT_STATE_CAL_WAIT_SELF_REF);
        }
        // 若此时状态仍为初始化的，标记为计算准备完成，参数装配完成
        if (definition.getState() == CalUnitBeanDefinition.CALUNIT_STATE_INITIALIZE) {
            definition.updateState(CalUnitBeanDefinition.CALUNIT_STATE_CAL);
        }
    }

    /**
     * 更新本地参数，获取外部依赖参数
     */
    private void updateLocalParameters() {
        // 获取外部依赖的参数信息
        Collection<T> externalUpdateParameters = bindUpdateParameters(externalParameterIds);
        // 解析参数信息，并添加到本地参数池
        analysisParameterBeans(externalUpdateParameters);
    }

    /**
     * 参数依赖查找
     *
     * @param id id
     * @return {@link ParameterBeanDefinition}
     */
    private ParameterBeanDefinition<T> lookupByLocalParams(Serializable id) {
        return localParameterBeans.get(id);
    }

    /**
     * 集合清除
     *
     * @param collection collect
     */
    private void clear(Collection<?> collection) {
        if (!collection.isEmpty()) {
            collection.clear();
        }
    }
}
